// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Actors;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Win32;

namespace WordCount
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private static string FabricConnectionString = "127.0.0.1:19000";
        private static bool UseGateway = false;

        private MapActor[] m_mapActors;
        private ReduceActor[] m_reduceActors;
        private AggregateActor m_aggregateActor;
        private CountStatusWindow m_statusWindow;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void ButtonChooseInput_Click(object sender, RoutedEventArgs e)
        {
            ListBoxFiles.Items.Clear(); // Clear out old entries
            var fileChooser = new OpenFileDialog();
            fileChooser.Multiselect = true;
            var initialPath = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.ToString();
            //var initialPath = Environment.CurrentDirectory + @"\..\..\";
            fileChooser.InitialDirectory = initialPath;
            //fileChooser.InitialDirectory = Directory.GetCurrentDirectory() + @"\..\..";
            fileChooser.Filter = "Text files (*.txt)|*.txt";
            bool? result = fileChooser.ShowDialog();

            if (result == true)
            {
                var filenames = fileChooser.FileNames;
                foreach (var name in filenames) this.ListBoxFiles.Items.Add(name);
            }
        }

        private void SliderMappers_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            var newValue = (int) e.NewValue;
            if(this.LabelMappers != null) this.LabelMappers.Content = "" + newValue;
        }

        private void SliderReducers_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            var newValue = (int)e.NewValue;
            if(this.LabelReducers != null) this.LabelReducers.Content = "" + newValue;
        }

        private Task SetupActorNetworkAsync(int numMappers, int numReducers, CountStatusWindow statusWindow)
        {
            // Clean up old network first, if applicable
            if (m_mapActors != null)
            {
                foreach (var actor in m_mapActors) 
                    if (actor != null)
                        actor.Dispose(); // should clean up client
                m_mapActors = null;
            }
            if (m_reduceActors != null)
            {
                foreach (var actor in m_reduceActors)
                    if (actor != null)
                        actor.Dispose();
                m_reduceActors = null;
            }
            if (m_aggregateActor != null)
            {
                m_aggregateActor.Dispose();
                m_aggregateActor = null;
            }

            return Task.Run(() =>
            {
                // Read your assembly info
                var myAssembly = Assembly.GetExecutingAssembly();
                var assemblyPath = myAssembly.Location;
                //var assemblyName = myAssembly.ManifestModule.Name;
                var assemblyName = myAssembly.FullName;
                byte[] buffer = File.ReadAllBytes(assemblyPath);

                var mapLauncher = Task.Run(() =>
                    {
                        m_mapActors = new MapActor[numMappers];
                        for (int i = 0; i < numMappers; i++)
                        {
                            m_mapActors[i] = new MapActor(i, numReducers);
                            var client = m_mapActors[i].DeployAndConnect(FabricConnectionString, UseGateway, 2);
                            client.Subscribe("Update", new UpdateObserver(ObserverType.Mapper, statusWindow));
                            m_mapActors[i].Initialize(client, assemblyName, buffer);
                            this.AddStatus("Mapper " + i + " launched and initialized");
                        }
                    });

                var reduceLauncher = Task.Run(() =>
                    {
                        m_reduceActors = new ReduceActor[numReducers];
                        for (int i = 0; i < numReducers; i++)
                        {
                            m_reduceActors[i] = new ReduceActor(i, numMappers);
                            var client = m_reduceActors[i].DeployAndConnect(FabricConnectionString, UseGateway, 2);
                            client.Subscribe("Update", new UpdateObserver(ObserverType.Reducer, statusWindow));
                            m_reduceActors[i].Initialize(client, assemblyName, buffer);
                            this.AddStatus("Reducer " + i + " launched and initialized");
                        }
                    });

                var aggregateLauncher = Task.Run(() =>
                {
                    m_aggregateActor = new AggregateActor(numReducers);
                    var client = m_aggregateActor.DeployAndConnect(FabricConnectionString, UseGateway, 2);
                    client.Subscribe("Update", new UpdateObserver(ObserverType.Aggregator, statusWindow));
                    m_aggregateActor.InitializeAggregator(client, assemblyName, buffer);
                    m_aggregateActor.SubscribeDone(new DoneObserver(this));
                });

                return Task.WhenAll(mapLauncher, reduceLauncher, aggregateLauncher);
            });
        }

        private async void ButtonStartCount_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                this.ButtonSearch.IsEnabled = false;
                this.TextBoxSearchResults.Clear();
                int numMappers = (int)this.SliderMappers.Value;
                int numReducers = (int)SliderReducers.Value;
                if (m_statusWindow != null) m_statusWindow.Close();
                m_statusWindow = new CountStatusWindow(numMappers, numReducers);
                await SetupActorNetworkAsync(numMappers, numReducers, m_statusWindow);
                StartWordCount();
                //m_statusWindow.Show();
                m_statusWindow.ShowDialog();
            }
            catch (Exception exc)
            {
                MessageBox.Show("Exception in start logic: " + exc);
            }
        }

        private async void StartWordCount()
        {
            //var files = this.ListBoxFiles.SelectedItems.Cast<string>().ToArray();
            var files = this.ListBoxFiles.Items.Cast<string>().ToArray();
            if (files.Length == 0)
            {
                MessageBox.Show("Must select at least one file to process");
                return;
            }
            int numMappers = (int)SliderMappers.Value;
            Task[] tasks = new Task[numMappers];
            for (int i = 0; i < numMappers; i++)
            {
                tasks[i] = Task.Factory.StartNew(/*async*/ obj =>
                {
                    var index = (int)obj;
                    for (int j = index; j < files.Length; j += numMappers)
                    {
                        var filename = files[j];
                        AddStatus("Sending " + filename + " to mapper " + index);
                        /*await*/ m_mapActors[index].MapAsync(filename).Wait();
                    }

                    m_mapActors[index].MarkComplete();
                }, i);
            }

            await Task.WhenAll(tasks);
        }

        internal void AddStatus(string status)
        {

            // Do nothing for now

            //this.Dispatcher.Invoke(() =>
            //{
            //    MessageBox.Show(status);
            //});

            //this.Dispatcher.Invoke(() =>
            //    {
            //        this.TextBoxStatus.AppendText("\r\n" + status);
            //    });
        }

        internal async void HandleDone()
        {
            AddStatus("WordCount Done!");
            List<KeyValuePair<string, int>> rankList = null;
            await Task.Run(() => { rankList = m_aggregateActor.GetRankedItems(0, 4); });
            StringBuilder sb = new StringBuilder("Top 5:\n");
            foreach (var kvp in rankList)
            {
                sb.AppendFormat("  {0}: {1}\n", kvp.Key, kvp.Value);
            }

            this.Dispatcher.Invoke(() => 
            {
                ButtonSearch.IsEnabled = true;
                MessageBox.Show(sb.ToString());
                if (m_statusWindow != null)
                {
                    m_statusWindow.Close();
                    m_statusWindow = null;
                }
            });
        }

        private async void ButtonSearch_Click(object sender, RoutedEventArgs e)
        {
            var searchItem = this.TextBoxSearch.Text.ToLower();
            var results = await Task.Run( () => m_aggregateActor.GetSearchResults(searchItem) );
            this.TextBoxSearchResults.Clear();
            if (results.Count > 0)
            {
                foreach (var result in results)
                {
                    var entry = String.Format("{0}: {1} occurrences, rank {2}", result.Item1, result.Item2, result.Item3);
                    this.TextBoxSearchResults.AppendText(entry + "\n");
                }
            }
            else this.TextBoxSearchResults.Text = "<No results found>";
        }
    }

    class DoneObserver : IObserver<ActorEvent>
    {
        private MainWindow m_parent;

        public DoneObserver(MainWindow parent)
        {
            m_parent = parent;
        }

        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
            throw new NotImplementedException();
        }

        public void OnNext(ActorEvent value)
        {
            m_parent.HandleDone();
            //m_parent.Dispatcher.Invoke(() => { MessageBox.Show("Done!"); });
        }
    }

    enum ObserverType
    {
        Mapper,
        Reducer,
        Aggregator
    }

    class UpdateObserver : IObserver<ActorEvent>
    {
        private CountStatusWindow m_statusWindow;
        private ObserverType m_observerType;
        public UpdateObserver(ObserverType observerType, CountStatusWindow statusWindow)
        {
            m_observerType = observerType;
            m_statusWindow = statusWindow;
        }

        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
            throw new NotImplementedException();
        }

        public void OnNext(ActorEvent value)
        {
            switch (m_observerType)
            {
                // Mapper update event is string payload and qualifier of int mapper index
                case ObserverType.Mapper:
                    m_statusWindow.UpdateMapperStatus(value.GetTypedPayloadQualifier<int>(0), value.GetTypedPayload<string>());
                    break;

                // Reducer update event is string payload and qualifier of int reducer index
                case ObserverType.Reducer:
                    m_statusWindow.UpdateReducerStatus(value.GetTypedPayloadQualifier<int>(0), value.GetTypedPayload<string>());
                    break;

                // Aggregator update event is string payload, no qualifiers
                case ObserverType.Aggregator:
                    m_statusWindow.UpdateAggregatorStatus(value.GetTypedPayload<string>());
                    break;
            }

        }
    }
}
